﻿using System.Collections.Generic;
using System.IO;
using System.ServiceModel.Syndication;
using System.Web;

namespace ServiceModel.Syndication.LEAP2A {
    /// <summary>
    /// LEAP2A feed error checking.
    /// </summary>
    public static class LeapFeedErrorChecker {
        /// <summary>
        /// Checks the specified feed for errors.
        /// This includes duplicate IDs, missing namespaces and incorrect links.
        /// </summary>
        /// <param name="feed">The atom feed.</param>
        /// <returns>A list of errors found in the feed.</returns>
        public static IList<LeapFeedError> Check(LeapFeed feed) {
            List<LeapFeedError> feedErrors = new List<LeapFeedError>();

            feedErrors.AddRange(DuplicateIdCheck(feed));
            feedErrors.AddRange(ContentCheck(feed));
            ////feedErrors.AddRange(NamespaceCheck(feed));
            feedErrors.AddRange(LinkCheck(feed));

            return feedErrors;
        }

        /// <summary>
        /// Checks for entries with duplicate IDs.
        /// </summary>
        /// <param name="feed">The atom feed.</param>
        /// <returns>A list of entries with duplicate IDs.</returns>
        private static List<LeapFeedError> DuplicateIdCheck(LeapFeed feed) {
            List<LeapFeedError> feedErrors = new List<LeapFeedError>();
            Dictionary<string, bool> lookup = new Dictionary<string, bool>();

            foreach (LeapItem item in feed.Items) {
                if (!lookup.ContainsKey(item.Id)) {
                    lookup.Add(item.Id, true);
                } else {
                    feedErrors.Add(new LeapFeedError("Entry ID '" + feed.UriToCurie(item.Id) + "' has already been used", item));
                }
            }

            return feedErrors;
        }

        /// <summary>
        /// Checks for entries with missing titles.
        /// </summary>
        /// <param name="feed">The atom feed.</param>
        /// <returns>A list of entries which have no content.</returns>
        private static List<LeapFeedError> ContentCheck(LeapFeed feed) {
            List<LeapFeedError> feedErrors = new List<LeapFeedError>();

            foreach (LeapItem item in feed.Items) {
                if (string.IsNullOrEmpty(LeapHelpers.GetAtomText(item.Title))) {
                    feedErrors.Add(new LeapFeedError("Entry with ID '" + feed.UriToCurie(item.Id) + "' has an empty title", item));
                }
            }

            return feedErrors;
        }

        /// <summary>
        /// Checks for the inclusion of the standard LEAP2A namespaces.
        /// </summary>
        /// <param name="feed">The feed.</param>
        /// <returns>A list of missing namespaces.</returns>
        private static List<LeapFeedError> NamespaceCheck(LeapFeed feed) {
            List<LeapFeedError> feedErrors = new List<LeapFeedError>();

            if (feed.LookupNamespacePrefix(feed.LeapTypesNamespace) == null) {
                feedErrors.Add(new LeapFeedError("Leap classes namespace is missing, add xmlns:" + feed.LeapTypesNamespacePrefix + "=\"" + feed.LeapTypesNamespace + "\""));
            }

            if (feed.LookupNamespacePrefix(feed.LeapPredicatesNamespacePrefix) == null) {
                feedErrors.Add(new LeapFeedError("Leap predicates namespace is missing, add xmlns:" + feed.LeapPredicatesNamespacePrefix + "=\"" + feed.LeapPredicatesNamespace + "\""));
            }

            if (feed.LookupNamespacePrefix(feed.LeapCategoryNamespace) == null) {
                feedErrors.Add(new LeapFeedError("Leap category namespace is missing, add xmlns:" + LeapCategorySchemes.NamespacePrefix + "=\"" + feed.LeapCategoryNamespace + "\""));
            }

            if (feed.LookupNamespacePrefix("http://www.w3.org/1999/02/22-rdf-syntax-ns#") == null) {
                feedErrors.Add(new LeapFeedError("RDF namespace is missing, add xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\""));
            }

            return feedErrors;
        }

        /// <summary>
        /// Checks for links which point to a missing entry and links which do not have a reverse link.
        /// </summary>
        /// <param name="feed">The atom feed.</param>
        /// <returns>A list of entries with broken links.</returns>
        private static List<LeapFeedError> LinkCheck(LeapFeed feed) {
            List<LeapFeedError> feedErrors = new List<LeapFeedError>();

            foreach (LeapItem item in feed.Items) {
                if (item.Content != null && item.Content.GetType() == typeof(UrlSyndicationContent)) {
                    UrlSyndicationContent content = item.Content as UrlSyndicationContent;

                    if (content != null) {
                        if (!content.Url.IsAbsoluteUri && feed.ImportBasePath != null) {
                            string path = Path.Combine(
                                feed.ImportBasePath, HttpUtility.UrlDecode(content.Url.ToString()));

                            if (!File.Exists(path)) {
                                feedErrors.Add(
                                    new LeapFeedError(
                                        "Missing local content file '" + content.Url + "'", item));
                            }
                        }
                    }
                }

                foreach (LeapLink link in item.Links) {
                    if (string.IsNullOrEmpty(link.RelationshipType)) continue;

                    LeapItem otherEnd = feed.LookupEntry(link.Uri);

                    LeapLinkPair linkType = feed.LeapLinkManager.LookupLinkPair(link.LeapRelationshipType);

                    if (linkType != null) {
                        if (otherEnd != null) {
                            bool okay = false;

                            foreach (LeapLink reverseLink in otherEnd.Links) {
                                if (reverseLink.Uri.ToString() == item.Id &&
                                    reverseLink.LeapRelationshipType == linkType.Inverse) {
                                    okay = true;
                                    break;
                                }
                            }

                            if (linkType.Value == LeapPredicates.AtomRelated) {
                                okay = true;
                            }

                            if (!okay) {
                                feedErrors.Add(new LeapFeedError("Item '" + feed.UriToCurie(otherEnd.Id) + "' is missing a link of type '" + feed.UriToCurie(linkType.Inverse) + "' to item '" + feed.UriToCurie(item.Id) + "'", otherEnd));
                            }
                        } else {
                            feedErrors.Add(new LeapFeedError("Item '" + feed.UriToCurie(item.Id) + "' has a link of type '" + feed.UriToCurie(link.RelationshipType) + "' to item '" + feed.UriToCurie(link.Uri.ToString()) + "' which is missing", otherEnd));
                        }
                    } else {
                        if (!link.Uri.IsAbsoluteUri && feed.ImportBasePath != null) {
                            string path = HttpUtility.UrlDecode(link.Uri.ToString());
                            path = Path.Combine(feed.ImportBasePath, path);

                            if (!File.Exists(path)) {
                                feedErrors.Add(
                                    new LeapFeedError("Missing local linked file '" + link.Uri + "'", item));
                            }
                        }
                    }
                }
            }

            return feedErrors;
        }
    }
}
